# 面经手册 · 第43篇《MyBatis 和 Spring 怎么整合?SqlSessionTemplate 和自动装配原理》

作者:小傅哥
博客:https://bugstack.cn (opens new window)

沉淀、分享、成长,让自己和他人都能有所收获!😄

# 一、前言

实际项目中,没有人会手写 SqlSessionFactoryBuilder 来使用 MyBatis。MyBatis 几乎总是和 Spring/Spring Boot 搭配使用。

但大多数人只是照着模板写配置,面试官问一句"SqlSessionTemplate 原理是什么"、"@MapperScan 是怎么扫描 Mapper 的",就答不上来了。

# 二、面试题

谢飞机,小记!,面试继续。

面试官:MyBatis 和 Spring 怎么整合的?

谢飞机:用 mybatis-spring 依赖。

面试官:整合的核心类有哪些?

谢飞机:SqlSessionFactoryBean?

面试官:SqlSessionTemplate 呢?它是线程安全的吗?

谢飞机:是线程安全的……

面试官:SqlSession 不是线程安全的吗?SqlSessionTemplate 怎么做到线程安全的?

谢飞机:代理?

面试官:Spring Boot 中 MyBatis 是怎么自动装配的?

谢飞机:starter?

面试官:具体说说我自动配置类做了什么?

谢飞机:不知道……再见!ヾ( ̄▽ ̄)

# 三、整合核心组件

# 1. mybatis-spring 关键类

┌─────────────────────────────────────────────┐
│           mybatis-spring 整合架构            │
├─────────────────────────────────────────────┤
│  SqlSessionFactoryBean                      │
│    → 创建 SqlSessionFactory                  │
│    → 读取 DataSource 和 configLocation      │
│    → 扫描 Mapper XML                        │
├─────────────────────────────────────────────┤
│  SqlSessionTemplate                         │
│    → SqlSession 的线程安全代理               │
│    → 每次操作创建新 SqlSession               │
│    → 自动参与 Spring 事务                   │
├─────────────────────────────────────────────┤
│  MapperScannerConfigurer                     │
│    → 扫描 Mapper 接口                        │
│    → 注册为 Spring BeanDefinition            │
├─────────────────────────────────────────────┤
│  MapperFactoryBean                          │
│    → 为每个 Mapper 接口创建代理 Bean          │
│    → 从 SqlSessionTemplate 获取 Mapper      │
└─────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2. 整合依赖

<!-- MyBatis-Spring 整合 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>

<!-- Spring Boot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 四、SqlSessionFactoryBean

# 1. 配置方式

XML 配置

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    <property name="typeAliasesPackage" value="com.example.entity"/>
</bean>
1
2
3
4
5
6

Spring Boot 配置

mybatis:
  config-location: classpath:mybatis-config.xml
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
1
2
3
4
5
6
7

# 2. 源码追踪

// org.mybatis.spring.SqlSessionFactoryBean
public class SqlSessionFactoryBean 
        implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<PooledConnectionAvailableEvent> {
    
    private DataSource dataSource;
    private Resource configLocation;
    private Resource[] mapperLocations;
    private String typeAliasesPackage;
    private Configuration configuration;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        // 1. 构建 Configuration
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
    
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        Configuration targetConfiguration;
        
        // 如果有 configLocation,从 XML 文件解析
        if (this.configLocation != null) {
            XMLConfigBuilder xmlConfigBuilder = 
                new XMLConfigBuilder(this.configLocation.getInputStream(), ...);
            targetConfiguration = xmlConfigBuilder.parse();
        }
        // 否则使用空 Configuration
        else {
            targetConfiguration = new Configuration();
        }
        
        // 设置 DataSource
        targetConfiguration.setEnvironment(
            new Environment("development", 
                new SpringManagedTransactionFactory(), dataSource));
        
        // 解析 mapperLocations(Mapper XML 文件)
        if (this.mapperLocations != null) {
            for (Resource mapperLocation : this.mapperLocations) {
                XMLMapperBuilder xmlMapperBuilder = 
                    new XMLMapperBuilder(mapperLocation.getInputStream(), 
                        targetConfiguration, ...);
                xmlMapperBuilder.parse();
            }
        }
        
        return new DefaultSqlSessionFactory(targetConfiguration);
    }
    
    @Override
    public SqlSessionFactory getObject() {
        return this.sqlSessionFactory;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 五、SqlSessionTemplate 原理

# 1. 为什么需要 SqlSessionTemplate

SqlSession 不是线程安全的(持有 Connection + 一级缓存)。在 Spring 环境中,Service 层是单例的,多个线程共享同一个 Mapper 代理,直接用 SqlSession 会出问题。

SqlSessionTemplate 就是解决线程安全的方案。

# 2. 核心源码

// org.mybatis.spring.SqlSessionTemplate
public class SqlSessionTemplate implements SqlSession, DisposableBean {
    
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;  // ← 代理对象
    
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());  // ← JDK 动态代理
    }
    
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }
    
    // 所有方法都委托给 sqlSessionProxy...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 3. SqlSessionInterceptor(核心)

// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 获取 SqlSession(从 Spring 事务同步器或新建)
        SqlSession sqlSession = getSqlSession(
            SqlSessionTemplate.this.sqlSessionFactory,
            SqlSessionTemplate.this.executorType,
            SqlSessionTemplate.this.exceptionTranslator);
        
        try {
            // 2. 执行方法
            Object result = method.invoke(sqlSession, args);
            
            // 3. 如果不是查询,标记为 dirty
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);  // 非事务环境下自动提交
            }
            
            return result;
        } catch (Throwable t) {
            // 4. 异常处理
            throw t;
        } finally {
            // 5. 关闭 SqlSession(非事务环境下)
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 4. getSqlSession 详解

// org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(
        SqlSessionFactory sessionFactory, 
        ExecutorType executorType, ...) {
    
    // 1. 从事务同步管理器获取(事务环境下复用同一个 SqlSession)
    SqlSessionHolder holder = (SqlSessionHolder) 
        TransactionSynchronizationManager.getResource(sessionFactory);
    
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;  // 事务中复用
    }
    
    // 2. 新建 SqlSession
    session = sessionFactory.openSession(executorType);
    
    // 3. 注册到事务同步管理器(事务环境下)
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        holder = new SqlSessionHolder(session);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(
            new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
    }
    
    return session;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5. 关键流程图解

非事务环境(每次新建):
  每次调用 → getSqlSession() → 新建 SqlSession
  → 执行 SQL → 自动 commit → 自动 close
  
事务环境(复用):
  第一次调用 → getSqlSession() → 新建 SqlSession → 绑定到 TransactionSynchronizationManager
  第二次调用 → getSqlSession() → 从同步管理器获取 → 复用同一个 SqlSession
  事务提交 → closeSqlSession() → 关闭
1
2
3
4
5
6
7
8

# 六、@MapperScan 原理

# 1. 配置方式

@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
1
2
3
4
5
6
7

# 2. 源码追踪

// org.mybatis.spring.annotation.MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MapperScannerRegistrar.class)  // ← 核心
public @interface MapperScan {
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<? extends Annotation> annotationClass() default Annotation.class;
    Class<?> markerInterface() default Class.class;
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
// org.mybatis.spring.mapper.MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
            BeanDefinitionRegistry registry) {
        // 读取 @MapperScan 属性
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes
            .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        // 配置扫描参数
        scanner.setAnnotationClass(mapperScanAttrs.getClass("annotationClass"));
        scanner.setMarkerInterface(mapperScanAttrs.getClass("markerInterface"));
        // ...
        
        // 扫描 basePackages
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// org.mybatis.spring.mapper.ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 1. 扫描接口
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
        
        // 2. 将每个接口注册为 MapperFactoryBean
        for (BeanDefinitionHolder holder : holders) {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            definition.setBeanClass(MapperFactoryBean.class);  // ← 关键
            // 设置 mapperInterface
            definition.getPropertyValues().add("mapperInterface", 
                definition.getBeanClassName());
        }
        
        return holders;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 3. MapperFactoryBean

// org.mybatis.spring.mapper.MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport 
        implements FactoryBean<T> {
    
    private Class<T> mapperInterface;
    
    @Override
    @SuppressWarnings("unchecked")
    public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
        // → SqlSessionTemplate.getMapper() → Configuration.getMapper()
        // → MapperRegistry.getMapper() → MapperProxyFactory.newInstance()
    }
    
    @Override
    public Class<T> getObjectType() {
        return this.mapperInterface;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 七、Spring Boot 自动装配

# 1. 自动配置类

// org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
    
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setMapperLocations(resolveMapperLocations());
        // ... 
        return factory.getObject();
    }
    
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2. 配置项映射

mybatis:
  config-location: classpath:mybatis-config.xml    → SqlSessionFactoryBean.setConfigLocation()
  mapper-locations: classpath:mapper/*.xml           → SqlSessionFactoryBean.setMapperLocations()
  type-aliases-package: com.example.entity           → SqlSessionFactoryBean.setTypeAliasesPackage()
  configuration:
    map-underscore-to-camel-case: true                → Configuration.setMapUnderscoreToCamelCase()
    cache-enabled: true                              → Configuration.setCacheEnabled()
    lazy-loading-enabled: true                       → Configuration.setLazyLoadingEnabled()
1
2
3
4
5
6
7
8

# 3. @Mapper 和 @MapperScan 区别

对比项 @Mapper @MapperScan
作用范围 接口级别 包级别
使用方式 每个接口加注解 配置类加一次
底层实现 MapperScannerConfigurer MapperScannerRegistrar
推荐 Mapper 少时 Mapper 多时(推荐)

# 八、常见面试追问

# Q1:SqlSessionTemplate 是线程安全的吗?

是。SqlSessionTemplate 内部通过 JDK 动态代理包装了一个 SqlSession 代理,每次方法调用都从 SqlSessionInterceptor 获取新的或事务绑定的 SqlSession,方法结束后自动关闭。SqlSessionTemplate 本身是无状态的,所有操作都委托给代理对象。

# Q2:MyBatis 事务和 Spring 事务的关系?

Spring 事务管理器管理 DataSource 的连接。在事务中,MyBatis 的 SqlSession 和 Spring 的数据库连接通过 TransactionSynchronizationManager 绑定到当前线程,实现事务一致性。事务提交时 Spring 提交连接并通知 MyBatis 关闭 SqlSession。

# Q3:@Mapper 和 @MapperScan 能一起用吗?

可以,但不建议。同时使用时,Mapper 可能被注册两次。建议只用 @MapperScan 统一管理。

# 九、总结

记住三个核心要点:

1. 整合核心三个类
   SqlSessionFactoryBean → 创建 SqlSessionFactory
   SqlSessionTemplate → 线程安全的 SqlSession 代理
   MapperScannerConfigurer / @MapperScan → 扫描 Mapper 接口

2. SqlSessionTemplate 线程安全原理
   JDK 动态代理 → 每次调用获取新/复用 SqlSession
   事务中:绑定到 TransactionSynchronizationManager 复用
   非事务:每次新建,执行完自动关闭

3. Spring Boot 自动装配
   MybatisAutoConfiguration 自动注册 SqlSessionFactory 和 SqlSessionTemplate
   yaml 配置 → MybatisProperties → SqlSessionFactoryBean 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

面试回答模板

MyBatis 和 Spring 的整合通过 mybatis-spring 实现,核心类有三个:SqlSessionFactoryBean 创建 SqlSessionFactory,SqlSessionTemplate 提供线程安全的 SqlSession 操作,MapperScannerConfigurer 或 @MapperScan 扫描 Mapper 接口注册为 Bean。

SqlSessionTemplate 的线程安全通过 JDK 动态代理实现。内部有一个 SqlSessionInterceptor,每次方法调用都从 SqlSessionUtils 获取 SqlSession——如果当前线程有事务绑定则复用,否则新建并在方法结束后关闭。SqlSessionTemplate 本身无状态,是线程安全的。

Spring Boot 中通过 MybatisAutoConfiguration 自动装配,根据 yaml 配置创建 SqlSessionFactory 和 SqlSessionTemplate,@MapperScan 通过 MapperScannerRegistrar 扫描接口并注册为 MapperFactoryBean,getObject() 时从 SqlSessionTemplate 获取 Mapper 代理。